// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/example-shopping
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import { repository } from '@loopback/repository';
import {
  post,
  param,
  get,
  requestBody,
  HttpErrors,
} from '@loopback/rest';
import { User, UserStatus } from '../models';
import { UserRepository, ChannelUserRepository, ConfigRepository } from '../repositories';
import { inject, Setter } from '@loopback/core';
import {
  authenticate,
  UserProfile,
  AuthenticationBindings,
} from '@loopback/authentication';
import { Credentials } from '../repositories/user.repository';
import { PasswordHasher } from '../services/hash.password.bcryptjs';
import { JWTAuthenticationService } from '../services/JWT.authentication.service';
import { JWTAuthenticationBindings, PasswordHasherBindings } from '../keys';
import { validateCredentials } from '../services/JWT.authentication.service';
import * as _ from 'lodash';
import { SmsLogRepository } from '../repositories/sms-log.repository';
import { getRandomNum, getCurTimestamp, sendEmail } from '../utils/utils';
import { RestBindings, Request } from '@loopback/rest';
var moment = require('moment');
var process = require('child_process');

// TODO(jannyHou): This should be moved to @loopback/authentication
const UserProfileSchema = {
  type: 'object',
  required: ['id'],
  properties: {
    id: { type: 'string' },
    email: { type: 'string' },
    name: { type: 'string' },
  },
};

type RegParams = {
  phone: string;
  password: string;
  code: string;
};

type QuickLoginParams = {
  phone: string;
  code: string;
  channel: string; // 从哪个渠道登录的
  clientType: string;
};

export class UserController {
  constructor(
    @repository(UserRepository) public userRepository: UserRepository,
    @repository(SmsLogRepository) protected smsLogRepo: SmsLogRepository,
    @repository(ChannelUserRepository) protected channelUserRepo: ChannelUserRepository,
    @repository(ConfigRepository) public configRepository: ConfigRepository,
    @inject.setter(AuthenticationBindings.CURRENT_USER)
    public setCurrentUser: Setter<UserProfile>,
    @inject(PasswordHasherBindings.PASSWORD_HASHER)
    public passwordHahser: PasswordHasher,
    @inject(JWTAuthenticationBindings.SERVICE)
    public jwtAuthenticationService: JWTAuthenticationService,
    @inject(RestBindings.Http.REQUEST)
    public req: Request
  ) { }

  @get('/users/{userId}', {
    responses: {
      '200': {
        description: 'User',
        content: {
          'application/json': {
            schema: {
              'x-ts-type': User,
            },
          },
        },
      },
    },
  })
  @authenticate('jwt')
  async findById(@param.path.string('userId') userId: string): Promise<User> {
    return this.userRepository.findById(userId, {
      fields: { password: false },
    });
  }

  @get('/users/me', {
    responses: {
      '200': {
        description: 'The current user profile',
        content: {
          'application/json': {
            schema: {
              'x-ts-type': User,
            },
          },
        },
      },
    },
  })
  @authenticate('jwt')
  async printCurrentUser(
    @inject('authentication.currentUser') currentUser: UserProfile,
  ): Promise<User> {
    return this.userRepository.findById(currentUser.id, {
      fields: { password: false },
    });;
  }

  @post('/users/login', {
    responses: {
      '200': {
        description: 'Token',
        content: {
          'application/json': {
            schema: {
              type: 'object',
              properties: {
                token: {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    },
  })
  // @authenticate('jwt', {action: 'generateAccessToken'})
  async login(
    @requestBody() credentials: Credentials,
  ): Promise<{ token: string }> {
    validateCredentials(credentials);
    const token = await this.jwtAuthenticationService.getAccessTokenForUser(
      credentials,
    );
    return { token };
  }

  @post('/users/quickLogin', {
    responses: {
      '200': {
        description: 'Token',
        content: {
          'application/json': {
            schema: {
              type: 'object',
              properties: {
                token: {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    },
  })
  async quickLogin(
    @requestBody() quickLoginParams: QuickLoginParams,
  ): Promise<{ user: User; token: string; code: Number }> {
    // 检查验证码是否正确
    let smsLog = await this.smsLogRepo.findOne({
      where: { phone: quickLoginParams.phone },
      order: ['id DESC'],
    });

    console.log(quickLoginParams);
    console.log(this.req.ip, this.req.ips);
    var confSms = await this.configRepository.findOne({where: {key: 'testSmsCode'}});
    if (confSms == null || confSms.value != '1') {
      if (smsLog == null || smsLog.code != quickLoginParams.code) {
        console.log(smsLog);
        throw new HttpErrors.UnprocessableEntity('验证码错误');
      }
    }

    let foundUser = await this.userRepository.findOne({
      where: { phone: quickLoginParams.phone },
    });

    if (!foundUser) {
      let user = new User();
      user.phone = quickLoginParams.phone;
      user.password = await this.passwordHahser.hashPassword('123456');
      user.nick = "用户" + getRandomNum(1000000, 9999999);
      let channelName = quickLoginParams.channel;
      let channelId = 0;

      if (channelName) {
        let channel = await this.channelUserRepo.findOne({ where: { name: channelName } });
        if (channel) {
          channelId = channel.id || 0;
        }
      }
      
      user.channelId = channelId;

      let curTs = getCurTimestamp();
      user.status = UserStatus.Active;
      user.activeTime = curTs;
      user.createTime = curTs;
      // user.ip = this.req.ip; // 有点问题，没生效，用下面的
      let ip = '';
      let addrsStr = this.req.header('X-Forwarded-For');
      if (addrsStr) {
        let addrs = addrsStr.split(',');
        if (addrs.length > 0) {
          ip = addrs[0];
        }
      }
      user.ip = ip;

      // ip检测
      let isOk = await this.checkIpLimit(user.phone, user.ip, channelName);
      if (!isOk) {
        // return { user: new User(), token: "", code: 403 };
      }

      // Save & Return Result
      foundUser = await this.userRepository.create(user);
      await this.addDailyReg(channelId)
    } else {
      console.log(foundUser)
      if (!foundUser.status || ~~foundUser.status != UserStatus.Active) {
        this.userRepository.updateById(foundUser.id, { status: UserStatus.Active, activeTime: getCurTimestamp() })
        var timeStamp = new Date().setHours(0,0,0,0) / 1000;
        console.log("激活用户", foundUser.phone, getCurTimestamp());
        if ( foundUser.createTime && foundUser.createTime >= timeStamp) {
          await this.addDailyActive(foundUser.channelId || 0);
        }
      }
    }

    delete foundUser.password;
    // Get token
    const token = await this.jwtAuthenticationService.getAccessTokenForUser(
      foundUser,
      false,
    );
    return { user: foundUser, token: token, code: 200 };
  }

  @post('/users/reg', {
    responses: {
      '200': {
        description: 'Token',
        content: {
          'application/json': {
            schema: {
              type: 'object',
              properties: {
                token: {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    },
  })
  async register(
    @requestBody() quickLoginParams: QuickLoginParams,
  ): Promise<{ code: number, token: string, jumpType: number }> {
    // 检查验证码是否正确
    let smsLog = await this.smsLogRepo.findOne({
      where: { phone: quickLoginParams.phone },
      order: ['id DESC'],
    });

    let retCode = 200;
    console.log(quickLoginParams);
    var confSms = await this.configRepository.findOne({where: {key: 'testSmsCode'}});
    if (confSms == null || confSms.value != '1') {
      if (smsLog == null || smsLog.code != quickLoginParams.code) {
        console.log(smsLog);
        throw new HttpErrors.UnprocessableEntity('验证码错误');
      }
    }

    let foundUser = await this.userRepository.findOne({
      where: { phone: quickLoginParams.phone },
    });

    let channelName = quickLoginParams.channel;
    let channelId = 0;
    let jumpType = 0;
    if (channelName) {
      let channel = await this.channelUserRepo.findOne({ where: { name: channelName } });
      if (channel) {
        channelId = channel.id || 0;
        jumpType = ~~channel.jumpType;
      }
    }

    if (!foundUser) {
      let user = new User();
      user.phone = quickLoginParams.phone;
      user.clientType = quickLoginParams.clientType;
      user.password = await this.passwordHahser.hashPassword('123456');
      user.nick = "用户" + getRandomNum(1000000, 9999999);
      user.channelId = channelId;

      let curTs = getCurTimestamp();
      user.createTime = curTs;
      if (jumpType == 0) {
        user.activeTime = curTs;
        user.status = 1;
      }

      let ip = '';
      let addrsStr = this.req.header('X-Forwarded-For');
      if (addrsStr) {
        let addrs = addrsStr.split(',');
        if (addrs.length > 0) {
          ip = addrs[0];
        }
      }
      user.ip = ip;

      // ip检测
      let isOk = await this.checkIpLimit(user.phone, user.ip, channelName);
      if (!isOk) {
        // return { code: 403 };
      }

      // Save & Return Result
      foundUser = await this.userRepository.create(user);

      // 每日扣量注册数
      if (channelId != 0) {
        await this.addDailyReg(channelId)
        await this.addDailyActive(channelId)
      }
    } else {
      retCode = 201
    }

    // Get token
    const token = await this.jwtAuthenticationService.getAccessTokenForUser(
      foundUser,
      false,
    );

    return { code: retCode, token: token, jumpType: jumpType };
  }

  async addDailyReg(channelId: number) {
    if (channelId == 0) {
      return;
    }
    let today = moment().format("YYYY-MM-DD")
    let channel = await this.channelUserRepo.findById(channelId);
    let registerCntTmp = await this.userRepository.dataSource.execute(`
      SELECT realRegisterCnt FROM ChannelDailyDownStat WHERE channelId=${channelId} and dateTime='${today}'
    `)

    let addCount = 1
    let activeStart = 10
    if (channel) {
      addCount = channel.activeProp2
      activeStart = channel.activeStart
    }
    let baseCnt = -1
    if (registerCntTmp.length != 0) {
      baseCnt = registerCntTmp[0].realRegisterCnt
    }
    if (baseCnt <= activeStart) {
      addCount = 1
    }
    console.log("addDailyReg_____registerCntTmp:", registerCntTmp, "channelId:", channelId, "baseCnt:", baseCnt, "activeStart:", activeStart, "addCount:", addCount)

    await this.userRepository.dataSource.execute(`
      INSERT INTO ChannelDailyDownStat(channelId, registerCnt, activeCnt, realRegisterCnt, realActiveCnt, dateTime) 
      VALUES (${channelId}, ${addCount}, 0, 1, 0, curdate())
      ON DUPLICATE KEY UPDATE registerCnt=registerCnt+${addCount}, realRegisterCnt=realRegisterCnt+1
    `)
  }

  async addDailyActive(channelId: number) {
    // 每日扣量激活数
    if (channelId != 0) {
      let today = moment().format("YYYY-MM-DD")
      let channel = await this.channelUserRepo.findById(channelId);
      let registerCntTmp = await this.userRepository.dataSource.execute(`
        SELECT realRegisterCnt FROM ChannelDailyDownStat WHERE channelId=${channelId} and dateTime='${today}'
      `)

      let actAddCount = 1
      let regAddCount = 0
      let activeStart = 10
      if (channel) {
        actAddCount = channel.activeProp1
        regAddCount = actAddCount
        activeStart = channel.activeStart
      }
      let baseCnt = -1
      if (registerCntTmp.length != 0) {
        baseCnt = registerCntTmp[0].realRegisterCnt
      }
      if (baseCnt <= activeStart) {
        actAddCount = 1
        regAddCount = 0
      }
      console.log("addDailyActive_____registerCntTmp:", registerCntTmp, "channelId:", channelId, "baseCnt:", baseCnt, "activeStart:", activeStart, "actAddCount:", actAddCount, "regAddCount:", regAddCount)
      await this.userRepository.dataSource.execute(`
        INSERT INTO ChannelDailyDownStat(channelId, registerCnt, activeCnt, realRegisterCnt, realActiveCnt, dateTime) 
        VALUES (${channelId}, ${actAddCount}, ${actAddCount}, 1, 1, curdate())
        ON DUPLICATE KEY UPDATE registerCnt=registerCnt+${regAddCount}, activeCnt=activeCnt+${actAddCount}, realActiveCnt=realActiveCnt+1
      `)
    }
  }

  async checkIpLimit(phone: string, userIp: string, channelName: string): Promise<boolean> {
    if (!userIp || userIp == "" || !channelName || channelName == "") {
      return true
    }

    let ipCountS = await this.userRepository.dataSource.execute(`select count(id) as count from User where ip='${userIp}' and createTime > UNIX_TIMESTAMP(CURRENT_DATE)`)
    let ipCount = 0;
    if (ipCountS.length != 0) {
      ipCount = ipCountS[0].count
    }
    let conf = await this.configRepository.findOne({ where: { key: 'IpDailyLimitCount' } })
    console.log("ip:", userIp, "ipCount:", ipCount, " IpDailyLimitCount:", conf);
    if (conf && Number(conf.value) > 0 && Number(conf.value) <= Number(ipCount)) {
      // 发邮件
      let configs = {
        senderHost: "smtp.qq.com",
        senderPort: 465,
        senderUser: "",
        senderPass: "",
        toUsers: ""
      }
      let conf = await this.configRepository.findOne({ where: { key: 'EmailSenderHost' } })
      if (conf) {
        configs.senderHost = conf.value
      }
      conf = await this.configRepository.findOne({ where: { key: 'EmailSenderPort' } })
      if (conf) {
        configs.senderPort = Number(conf.value)
      }
     conf = await this.configRepository.findOne({ where: { key: 'EmailSenderUser' } })
      if (conf) {
        configs.senderUser = conf.value
      }
      conf = await this.configRepository.findOne({ where: { key: 'EmailSenderPass' } })
      if (conf) {
        configs.senderPass = conf.value
      }
      conf = await this.configRepository.findOne({ where: { key: 'EmailToUsers' } })
      if (conf) {
        configs.toUsers = conf.value
      }
      sendEmail(configs.senderHost, configs.senderPort, configs.senderUser, configs.senderPass, configs.toUsers, "IP注册告警", `用户(${phone})渠道(${channelName})同一IP(${userIp})注册超过${ipCount}次`);
      return false
    }
    return true
  }
}


